
"""销售单的改造"""

from odoo import models, api, fields, _
from odoo.tools import float_compare
from odoo.exceptions import UserError, ValidationError


class GSPCheck(models.AbstractModel):
    _name = 'gsp.business.check'
    _description = '业务中的GSP管理'

    nd_avail_gsp_categ_ids = fields.Many2many('gsp.product.categ', string="此客户下允许的GSP产品类别",
                                              compute="_compute_nd_avail_gsp_categ_ids", store=True)
    nd_avail_product_ids = fields.Many2many('product.product', string="此客户下允许的产品",
                                              compute="_compute_nd_avail_gsp_categ_ids", store=True)
    nd_avail_gsp_dosage_ids = fields.Many2many('gsp.dosage.categ', string="此客户下允许的GSP剂型类别",
                                               compute="_compute_nd_avail_gsp_categ_ids", store=True)
    nd_avail_instrument_categ_ids = fields.Many2many('gsp.instrument.categ', string="此客户下允许的器械分类",
                                               compute="_compute_nd_avail_gsp_categ_ids", store=True)

    @api.depends('partner_id')
    def _compute_nd_avail_gsp_categ_ids(self):
        """根据客户的变更而计算客户允许的经营范围"""
        for order in self:
            partner_id = order.partner_id
            if partner_id and partner_id.gsp_management:
                operation_categ_ids = partner_id.nd_license_ids.mapped('product_categ_ids')
                nd_avail_product_ids = partner_id.nd_license_ids.mapped('product_ids')
                dosage_categ_ids = partner_id.nd_license_ids.mapped('dosage_ids')
                instrument_categ_ids = partner_id.nd_license_ids.mapped('instrument_categ_ids')
            else:
                operation_categ_ids = self.env['gsp.product.categ'].search([])
                nd_avail_product_ids = self.env['product.product'].search([])
                dosage_categ_ids = self.env['gsp.dosage.categ'].search([])
                instrument_categ_ids = self.env['gsp.instrument.categ'].search([])

            order.update({'nd_avail_gsp_categ_ids': [(6, 0, operation_categ_ids.ids)],
                          'nd_avail_product_ids': [(6, 0, nd_avail_product_ids.ids)],
                          'nd_avail_gsp_dosage_ids': [(6, 0, dosage_categ_ids.ids)],
                          'nd_avail_instrument_categ_ids': [(6, 0, instrument_categ_ids.ids)]})

    @api.model
    def _nd_check_price(self, product):
        """弹出对于产品的历史价格"""
        product.ensure_one()
        info = ''
        if self._name == 'purchase.order':
            info = f'最高价格：{product.nd_top_purchase_price} \n' \
                   f'最低价格：{product.nd_low_purchase_price} \n' \
                   f'最近价格：{product.nd_recent_purchase_price} \n' \
                   f'平均价格：{round(product.nd_avg_purchase_price, 2)}'
        elif self._name == 'sale.order':
            info = f'最高价格：{product.nd_top_sale_price} \n' \
                   f'最低价格：{product.nd_low_sale_price} \n' \
                   f'最近价格：{product.nd_recent_sale_price} \n' \
                   f'平均价格：{round(product.nd_avg_sale_price, 2)}'
        raise UserError(info)

    def apply_gsp_manage(self):
        """如果客户是GSP管理的，就检查交易产品是否在他的交易范围内，并检查产品是否在我方的交易范围内，
        并检查交易双方是否有过期的证照"""
        for order in self:
            # 检查过期证照
            self._check_license_expire_date(order.partner_id)

            # 先检查客户的交易许可范围
            if order.partner_id.gsp_management:
                # 8.24 修改增加product_ids
                available_categ_ids, product_ids, available_dosage_ids, instrument_categ_ids = self._get_available_categ_and_dosage(order.partner_id)
                product_ids = order.order_line.mapped('product_id')
                for product_id in product_ids:
                    if product_id.gsp_categ_id not in available_categ_ids and \
                            product_id not in product_ids and \
                            product_id.dosage_id not in available_dosage_ids and \
                            product_id.instrument_categ_id not in instrument_categ_ids:
                        raise ValidationError(f'交易产品“{product_id.display_name}”不在客户的许可范围内！')

            # 检查我方的交易许可范围
            myself_partner_id = order.sudo().company_id.partner_id
            if myself_partner_id and myself_partner_id.gsp_management:
                available_categ_ids, product_ids, available_dosage_ids, instrument_categ_ids = self._get_available_categ_and_dosage(myself_partner_id)
                product_ids = order.order_line.mapped('product_id')
                for product_id in product_ids:
                    if product_id.gsp_categ_id not in available_categ_ids and \
                            product_id not in product_ids and \
                            product_id.dosage_id not in available_dosage_ids and \
                            product_id.instrument_categ_id not in instrument_categ_ids:
                        raise ValidationError(f'交易产品“{product_id.display_name}”不在我司的许可范围内！')

    def _check_license_expire_date(self, partners, date=False):
        """检查指定用户在指定的日期当天是否有过期的证照"""
        if not date:
            date = fields.Date.today()

        for partner in partners:
            expired_licenses = partner.nd_license_ids.filtered(lambda x: x.expire_date and x.expire_date < date)
            if expired_licenses:
                raise UserError(f'合作伙伴：{partner.display_name}的证照：{expired_licenses[0].display_name} '
                                f'已于 {expired_licenses[0].expire_date} 过期')

    def _get_available_categ_and_dosage(self, partner_id):
        """获取指定客户允许的经营类型和剂型"""
        categ_ids = self.env['gsp.product.categ']
        dosage_ids = self.env['gsp.dosage.categ']
        product_ids = self.env['product.product']
        instrument_categ_ids = self.env['gsp.instrument.categ']
        if partner_id:
            categ_ids = partner_id.nd_license_ids.mapped('product_categ_ids')   # 允许的产品类型
            product_ids = partner_id.nd_license_ids.mapped('product_ids')   # 允许的产品
            dosage_ids = partner_id.nd_license_ids.mapped('dosage_ids')         # 允许的剂型
            instrument_categ_ids = partner_id.nd_license_ids.mapped('instrument_categ_ids')     # 允许的器械分类
        return categ_ids, product_ids, dosage_ids, instrument_categ_ids

    def _get_qty_available_for_product(self, product):
        """获取产品的可用数量"""
        product.ensure_one()
        location_ids = self._get_legal_locations()
        Quant = self.env['stock.quant']
        qty = 0
        for location_id in location_ids:
            qty += Quant._get_available_quantity(product_id=product, location_id=location_id)
        return qty

    def _get_legal_locations_domain(self):
        return [('usage', '=', 'internal'), ('scrap_location', '!=', True)]

    def _get_legal_locations(self, return_raw_domain=False):
        """返回合格品仓库，主要是要从内部位置中去除报废、不合格品、寄售等库位
        return_raw_domain的作用是在某些视图里也需要筛选出合格品库位，可以直接由此返回筛选条件
        TODO 部分特性是在后续模块里添加的，所以此方法理论上应当在后续模块里扩展，而不是在这里统一添加
        """
        # domain = [('usage', '=', 'internal'), ('scrap_location', '!=', True),
        #           ('is_consignment', '!=', True), ('unqualified_location', '!=', True)]
        domain = self._get_legal_locations_domain()
        if return_raw_domain:
            return domain
        locations = self.env['stock.location'].search(domain)
        return locations


class SaleOrder(models.Model):
    _name = 'sale.order'
    _inherit = ['sale.order', 'gsp.business.check']

    @api.model
    def create(self, vals):
        res = super().create(vals)
        res.apply_gsp_manage()
        return res

    @api.multi
    def write(self, vals):
        res = super().write(vals)
        self.apply_gsp_manage()
        return res

    @api.multi
    def action_confirm(self):
        """在原生基础上，记录销售价格信息，并检查税率的填写"""
        self.check_line_tax()
        for sale in self:
            self.env['product.template'].update_nd_sale_price(sale.order_line)
        return super().action_confirm()

    def check_line_tax(self):
        """检查是否填写税率"""
        no_tax_lines = self.mapped('order_line').filtered(lambda x: not x.tax_id)
        if no_tax_lines:
            raise UserError(f'有销售明细未填写税率：{no_tax_lines[0].product_id.name}')


class SaleOrderLine(models.Model):
    _inherit = 'sale.order.line'

    nd_producer_id = fields.Many2one('gsp.producer', string="生产厂家", store=True,
                                     related="product_id.producer_id")
    # gift = fields.Boolean('赠品')
    nd_size = fields.Char('产品规格', store=True,related="product_id.nd_size")
    update_product_price = fields.Boolean('已更新产品价格', help="标记此明细是否已更新产品历史价格信息，防止重复")

    # approve_date = fields.Date('批准日期', related="product_id.approve_date", store=True)
    # expire_date = fields.Date('有效期至', related="product_id.expire_date", store=True)
    # 在nd_present/models/model.py里重写这两个字段
    approve_date = fields.Date('批准日期')
    expire_date = fields.Date('有效期至')
    nd_qty = fields.Float('总可用数量', compute="_compute_nd_qty", store=True)

    @api.multi
    def nd_check_history_price(self):
        """弹出对于产品的历史价格"""
        self.ensure_one()
        if self.product_id:
            return self.env['sale.order']._nd_check_price(self.product_id)

    @api.depends('product_id')
    def _compute_nd_qty(self):
        for line in self:
            if line.product_id:
                line.nd_qty = line.order_id._get_qty_available_for_product(line.product_id)

    @api.multi
    @api.onchange('product_id')
    def product_id_change(self):
        res = super(SaleOrderLine, self).product_id_change()
        # 需求：销售单填充此客户最后一次的价格
        # 查找客户该产品所有的销售明细，用最近的填写价格
        if not all([self.product_id, self.order_partner_id]):
            return res
        all_lines = self.search([('order_partner_id', '=', self.order_partner_id.id),
                                 ('product_id', '=', self.product_id.id)], order='id desc')
        if all_lines:
            # 明细单价为客户最后一次的价格
            self.price_unit = all_lines[0].price_unit
        # 需求：冷链产品和非冷链产品不能坐在一张销售单据上
        # 选择产品时进行判断
        self.validate_product_legal()
        return res

    def validate_product_legal(self):
        """判断冷链产品和非冷链产品不能坐在一张销售单据上"""
        for res in self:
            all_lines = res.order_id.order_line
            product_lines = all_lines.filtered(lambda x: x.product_id and x.product_id.is_cold_chain_medicine == False)
            cold_lines = all_lines.filtered(lambda x: x.product_id and x.product_id.is_cold_chain_medicine)
            if product_lines and cold_lines:
                raise ValidationError(_('冷链和非冷链的不能做在一张销售单据上，请重新选择当前产品'))

    @api.onchange('product_uom', 'product_uom_qty')
    def product_uom_change(self):
        res = super(SaleOrderLine, self).product_uom_change()
        if not all([self.product_id, self.order_partner_id]):
            return res
        all_lines = self.search([('order_partner_id', '=', self.order_partner_id.id),
                                 ('product_id', '=', self.product_id.id)], order='id desc')
        if not all_lines:
            return res
        # 历史价格
        price_unit = all_lines[0].price_unit
        # 根据明细更改单位改变价格
        ratio = self.get_ratio(self.product_uom, self.product_id)
        self.price_unit = price_unit * ratio
        return res

    def get_ratio(self, uom_id, product):
        ratio = 1
        product_ratio = 1
        if not all([uom_id, product]):
            return ratio
        product_uom = product.uom_id
        # 两单位相等直接返回
        if uom_id == product_uom:
            return ratio
        # 得到明细单位对于标准单位的比率
        if uom_id.uom_type == 'bigger':
            ratio = uom_id.factor_inv * ratio
        elif uom_id.uom_type == 'smaller':
            ratio = 1/uom_id.factor
        # 得到产品单位对于标准单位的比率
        if product_uom.uom_type == 'bigger':
            product_ratio = uom_id.factor_inv * ratio
        elif product_uom.uom_type == 'smaller':
            product_ratio = 1 / uom_id.factor
        # 得到当前单位比率 / 产品单位比率 的值，例如明细单位为打(12件)，产品单位为件，标准单位为件，最后比率为12，当明细改变单位为打，单价*12
        ratio = ratio / product_ratio
        return ratio



